package com.monkey4flux.security.config;

import com.alibaba.fastjson.JSONObject;
import com.monkey4flux.security.handler.JwtSecurityContextRepository;
import com.monkey4flux.security.service.UserService;
import com.monkey4flux.security.util.JwtTokenUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.authentication.*;
import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;

/**
 * @Description EnableWebFluxSecurity权限验证配置
 * @Author JL
 * @Version V1.0
 */
@Slf4j
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
@ComponentScan(basePackages = {"com.monkey4flux.security.service"})
public class WebFluxSecurityConfigurer {

    /**
     * 提供用于获取UserDetails的Service
     *
     * @param userService
     * @return
     */
    @Bean
    public ReactiveAuthenticationManager authenticationManager(UserService userService) {
        log.info("加载security 用户配置....");
        return new UserDetailsRepositoryReactiveAuthenticationManager(userService);
    }

    /**
     * http请求路径权限与过滤链配置
     *
     * @param http
     * @param userService
     * @return
     */
    @Bean
    public SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http, UserService userService) {
        log.info("加载security 权限配置....");
        http
                .csrf().disable()
                .cors().disable()
                .httpBasic().disable()
                .securityContextRepository(new JwtSecurityContextRepository(userService))
                .formLogin()
                .authenticationFailureHandler((webFilterExchange, exception) -> { //验证失败处理器(可以单独创建类处理)
                    webFilterExchange.getExchange().getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
                    Map<String, String> responseMap = new HashMap<>();
                    responseMap.put("code", "failure");
                    if (exception instanceof UsernameNotFoundException) {
                        responseMap.put("msg", "用户不存在 " + exception.getMessage());
                    } else if (exception instanceof BadCredentialsException) {
                        responseMap.put("msg", "密码错误 " + exception.getMessage());
                    } else if (exception instanceof LockedException) {
                        responseMap.put("msg", "用户锁定 " + exception.getMessage());
                    } else if (exception instanceof AccountExpiredException) {
                        responseMap.put("msg", "账户过期 " + exception.getMessage());
                    } else if (exception instanceof DisabledException) {
                        responseMap.put("msg", "账户不可用 " + exception.getMessage());
                    } else {
                        responseMap.put("msg", "系统错误 " + exception.getMessage());
                    }
                    responseMap.put("msg", exception.getMessage());
                    return writeWith(webFilterExchange.getExchange(), responseMap);
                })
                .loginPage("/login")
                .authenticationSuccessHandler((webFilterExchange, authentication) -> { //验证成功处理器(可以单独创建类处理)
                    User user = (User) authentication.getPrincipal();
                    userService.saveCacheUser(user);
                    String token = JwtTokenUtils.createToken(user.getUsername(), -1);
                    Map<String, String> responseMap = new HashMap<>();
                    responseMap.put("code", "success");
                    responseMap.put("data", token);
                    return writeWith(webFilterExchange.getExchange(), responseMap);
                }).and()
                .authorizeExchange()//请求进行授权
                .pathMatchers(HttpMethod.OPTIONS).permitAll()//特殊请求过滤
                .pathMatchers("/login").permitAll()//登录不需要验证
                .anyExchange()//任何请求
                .authenticated()//都需要身份认证
                .and().logout()
                .logoutSuccessHandler((webFilterExchange, authentication) -> { //退出成功处理器(可以单独创建类处理)
                    Map<String, String> responseMap = new HashMap<>();
                    responseMap.put("code", "logout");
                    responseMap.put("msg", "退出成功");
                    return writeWith(webFilterExchange.getExchange(), responseMap);
                }).and()
                .exceptionHandling()
                .accessDeniedHandler((exchange, denied) -> { // 无权限访问处理器(可以单独创建类处理)
                    Map<String, String> responseMap = new HashMap<>();
                    responseMap.put("code", "denied");
                    responseMap.put("msg", "账户无权限访问");
                    return writeWith(exchange, responseMap);
                });

        return http.build();
    }

    /**
     * 输出响应信息
     *
     * @param exchange
     * @param responseMap
     * @return
     */
    public Mono<Void> writeWith(ServerWebExchange exchange, Map<String, String> responseMap) {
        ServerHttpResponse response = exchange.getResponse();
        String body = JSONObject.toJSONString(responseMap);
        DataBuffer buffer = null;
        try {
            buffer = response.bufferFactory().wrap(body.getBytes("UTF-8"));
        } catch (UnsupportedEncodingException ue) {
            ue.printStackTrace();
        }
        return response.writeWith(Mono.just(buffer));
    }

}
